成品連結:Hold Shift and Check Checkboxes、程式碼
今天要做的是選單多重選取!這功能就像是當你在資料夾選取一個項目,然後按著 shift 並選取另一個檔案,會發現兩個檔案之間的所有檔案都變成選取狀態了。今天我們要做的就是這個功能
關於今天的主題我的想法是:
當然還需要考慮到使用者是否有按下 shift (有按下 shift 的狀態才會選取區間內的 checkbox),但話不多說我們就開始吧!
:::warning
:zap: 程式碼由於拆解看起來可能有點亂,完整版請見 GitHub 程式碼連結
:::
從 CSS 可以看到當 input[type="checkbox"]
被勾選後,同一層的 p
會被畫線,所以們我只需要單純處理 input[type="checkbox"]
的狀態就好而不需要去管其它元素
input:checked + p {
background: #F9F9F9;
text-decoration: line-through;
}
首先先選取全部的 input[type="checkbox"]
const checkboxes = document.querySelectorAll('input');
接著建立一個變數來辨識使用者是否有按下 shift,預設 false
,接著使用監聽事件來監測狀態:如果事件是 keydown
且按下 shift,則變數變為 true
;反之當 keyup
時且無按下 shift,則變數變為 false
let holdingShift = false;
window.addEventListener('keydown', function(e) {
if (e.keyCode === 16) { // keyCode 16 代表 shift
holdingShift = true;
}
});
window.addEventListener('keyup', function(e) {
holdingShift = false;
});
接著建立新變數來儲存最後打勾的 checkbox
let lastChecked;
使用 let
而不是 const
是因為之後值會變動
然後為每個 checkbox 設定監聽事件,並把被點取的 checkbox 存入 lastChecked
變數
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener('click', function(e) {
// 執行打勾功能
lastChecked = checkBoxes[index];
}
});
這裡開始要做一些判斷了,當 lastChecked
已被打勾且使用者按下 shift 時,將 lastChecked
與目前點選項目之間的元素存入新的陣列
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener('click', function(e) {
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastChecked = checkBoxes[index];
}
});
此時會發現當按下 checkbox 時遇到錯誤:Uncaught TypeError: Cannot read property 'checked' of undefined
,這是因為一開始我們並沒有賦予 lastChecked
值,故目前還是 undefined
,所以需要另做處理
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener('click', function(e) {
// 當 lastChecked 是 undefined 時,lastChecked = 被點選的元素
if (lastChecked === undefined) {
lastChecked = checkbox;
}
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastChecked = checkBoxes[index];
}
});
newList
的元素至此我們已經有了當前點擊元素 & 前一次點擊的元素(lastChecked
),可以來補充 newList
的內容了!
我的想法是比較當前點擊元素 & 前一次點擊的元素(lastChecked
)在 checkBoxes
陣列中的位置,並把在中間的元素加入新陣列。有什麼方法可以截取陣列的部分?
沒錯,就是使用 slice()
!但 slice()
並不是 nodeList
可用的 method,所以需要先將原本的 checkBoxes
轉成陣列
為了要判斷順序,需要在全域再宣告新的變數 lastIndex
來紀錄上次點選元素的 index,並且在監聽事件中與 lastChecked
一同更新
const checkBoxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));
let holdingShift = false;
let lastIndex;
let lastChecked;
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener('click', function(e) {
// 當 lastChecked 是 undefined 時,lastChecked = 被點選的元素
if (lastChecked === undefined) {
lastChecked = checkbox;
}
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastIndex = index;
lastChecked = checkBoxes[index];
}
});
有時候可能會先點後方項目再點擊前面項目(例如點擊第 5 項再點擊第 2 項),所以要將各種情況做不同處理
if (index < lastIndex) {
newList = checkBoxes.slice(index, lastIndex);
} else if (lastIndex < index) {
newList = checkBoxes.slice(lastIndex, index);
} else return; // 當 index = lastIndex 時直接 return ,不然會做出長度為 0 的陣列
接著就把 newList
內全部的項目打勾即可!
newList.forEach(checkbox => {
checkbox.checked = true;
console.log(checkbox);
});
完成!
這裡順便補充一下右鍵的觸發事件;左鍵我們都知道是 click
,而右鍵是 contextmenu
喔!